/*
    Speedbump aka Project Kimbula
    Copyright (C) 2010  Ping Shin Ching

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see http://www.gnu.org/licenses.

	Ping Shin Ching
	righteous.ninja@gmail.com OR ping_sc@hotmail.com
	More information at http://www.tidytutorials.com/p/speedbump.html
 */


/*************************************************************************
 *TAKE EXTRA CARE WHEN MODIFYING THIS FILE. ERRORS WILL TAKE DAYS TO DEBUG
	
	upstream read	->	handledata	->	upstream write
client			csock      		csock2		web-server
	downstream write<-	handledata2	<-	downsream read

 ************************************************************************/
#include "application.h"

#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <sys/time.h>
#include <sys/types.h>
#include <errno.h>
#include <unistd.h>
#include <netdb.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#include <sys/socket.h>
#include "logger.h"
#include "rules.h"
#include "responsehelper.h"
#include "helpers.h"

#include "httphandler.h"
#include "protocolhttphandler.h"

#include "mycap.h"

/*************************************************************************
 * Destructor 
 *************************************************************************/
ProtocolHTTPHandler::~ProtocolHTTPHandler(){

}


/*************************************************************************
 * The child process, called after fork
 *************************************************************************/
void* ProtocolHTTPHandler::Run(ProtocolHandlerParams* sp){

	char tmps128[128];

	fd_set rFd;
	fd_set wFd;
	fd_set eFd;
	
	fd_set rFdx;
	fd_set wFdx;
	fd_set eFdx;

	ProtocolHTTPParams* php = (ProtocolHTTPParams*)sp->param1;

	PCORE_NEXTPROCESS nextoperation;

	struct timeval stimeout;
	struct timeval stimeoutx;
	
	int retvs, retv, retv2;

	SOCKET csock2, csock;
	

	char bufferrecv[BUFFER_SIZE];
	char bufferrecv2[BUFFER_SIZE2];

	char buffersend[BUFFER_SIZE];
	char buffersend2[BUFFER_SIZE2];


	int	 bufferbytesrecv	= 0;	
	int	 bufferbytesrecv2	= 0;	

	int	 bufferbytessend	= 0;	
	int	 bufferbytessend2	= 0;	

	HTTPHandler handlerdata(HANDLER_UPSTREAM), handlerdata2(HANDLER_DOWNSTREAM);

	int bytecounter=0;

	void* capi_param = 0;

	csock = sp->csock;

	stimeout.tv_sec	= php->inactivetimeout;
	stimeout.tv_usec= 0 ;
	

	FD_ZERO(&rFd);
	FD_ZERO(&wFd);
	FD_ZERO(&eFd);
	

	if(InitSocket(&csock2, SOCK_0)!=0){
		goto TERMINATE;
	}
	

	if(sp->redirecturl){
		handlerdata.redirecturl = (char*)malloc((strlen(sp->redirecturl)+1)*sizeof(char));
		memset(handlerdata.redirecturl, 0, strlen(sp->redirecturl)+1);
		if(handlerdata.redirecturl){
			strcpy(handlerdata.redirecturl, sp->redirecturl);
		}
	}
	else{
		
	}

	memcpy(&(handlerdata.remoteaddr), &(sp->remoteaddr), sizeof(ipaddress));
	handlerdata.procid= sp->pid;

	handlerdata.ratelimit	= handlerdata2.ratelimit	= sp->ratelimitdown;
	handlerdata.param1	= handlerdata2.param1		= sp->param1;
	handlerdata.capi_param1 = handlerdata2.capi_param1	= &capi_param;
	handlerdata.cascadeon 	= handlerdata2.cascadeon	= php->proxyport?1:0;
	handlerdata.fullforward = handlerdata2.fullforward	= php->fullforward;
	handlerdata.rateadjustment = handlerdata2.rateadjustment = sp->rateadjustment;

	handlerdata.otherHandler = &handlerdata2;
	handlerdata2.otherHandler = &handlerdata;

	if(php->fullforward)
		nextoperation = PCORE_REMOTE_DIRECT_CONNECT;
	else
		nextoperation = PCORE_REMOTE_CONNECTING;

	FD_SET(csock, &rFd);
	FD_SET(csock, &eFd);
	
	bufferbytessend = 0;
	bufferbytessend2 = 0;

	while(1){
		//When new connection to remote host is needed, dirtybit is true
		if(nextoperation==PCORE_REMOTE_RECONNECTING||nextoperation==PCORE_REMOTE_DIRECT_CONNECT){
			if(InitSocket(&csock2, SOCK_0)==0 && ((!php->fullforward && OpenConnectionToHost(&csock2, &handlerdata,0,0)!=0) ||
			 (php->fullforward && OpenConnectionToHost(&csock2, php->proxyaddress, php->proxyport)!=0))){
				loge<<"ProtocolCore: S-socket open to C-socket failed Recon"<<lend;
				goto TERMINATE;
			}
			if(handlerdata.connectmethod && !php->proxyport){
				handlerdata.KillBuffer();
				nextoperation=PCORE_REMOTE_SSL_CONNECTING;
			}
			else{
				nextoperation=PCORE_REMOTE_CONNECTING;
			}
			FD_SET(csock2, &rFd);
			FD_SET(csock2, &eFd);
			FD_SET(csock2, &wFd);
		}

		memcpy(&rFdx, &rFd, sizeof(rFd));
		memcpy(&wFdx, &wFd, sizeof(wFd));
		memcpy(&eFdx, &eFd, sizeof(eFd));
		memcpy(&stimeoutx, &stimeout, sizeof(stimeout));

		logd<<"Before the select " <<lend;
		if( (retvs = select( (csock>csock2?csock:csock2)+1, &rFdx, &wFdx, &eFdx, &stimeoutx) ) > 0){

			//1 Upstream Read - Proxy reading from client 
			if(FD_ISSET(csock, &rFdx)){
				logd<<"ProtocolCore: Upstream read"<<lend;
				if((bufferbytesrecv = recv(csock, bufferrecv, BUFFER_SIZE,0))==-1){
					loge<<"ProtocolCore: ProtocolCore: Upstream recv error - "<<errno<<lend;
					goto TERMINATE;
				}
				else if(bufferbytesrecv == 0){
					logd<<"ProtocolCore: Upstream read close"<<lend;
					//FD_CLR(csock, &rFd);
					goto TERMINATE;

				}
				else{
					bytecounter+=bufferbytesrecv;
					///**NEW**/handlerdata2.ResetHeaderCapture();
					logd<<"ProtocolCore: Upstream recv "<<bufferbytesrecv<<lend;
					switch(handlerdata.HandleData( bufferrecv, &bufferbytesrecv)){
						case HANDLER_FINISHED:
							logd<<"ProtocolCore: Upstream  handler finished"<<lend;
							handlerdata2.ResetHeaderCapture();

							if(sp->redirecturl){
								int mbuflen = strlen(sp->redirecturl)+256;
								char mbuf[mbuflen];
								ShortCircuit(&csock2, &csock, &rFd, &wFd, &eFd, &handlerdata2, 
									ResponseHelper::CreateMessageOK(mbuf, mbuflen, sp->redirecturl));
								FD_SET(csock, &wFd);
								break;
							}
							if(!php->proxyport && handlerdata.dirtybit){
								logd<<"ProtocolCore: DIRTY BIT SET"<<lend;
								handlerdata.dirtybit=0;
								CloseSocket(&csock2);
								nextoperation = PCORE_REMOTE_RECONNECTING;
								continue;
							}
							else if(nextoperation==PCORE_REMOTE_CONNECTING){
								//InitSocket(&csock2, SOCK_0);
								if((!php->proxyport && OpenConnectionToHost(&csock2, &handlerdata,0,0)!=0) ||
									(php->proxyport && OpenConnectionToHost(&csock2, php->proxyaddress, php->proxyport)!=0)){
									loge<<"ProtocolCore: S-socket open to C-socket failed"<<lend;
									
									if(php->fullforward)
										goto TERMINATE;
									else{
										ShortCircuit(&csock2, &csock, &rFd, &wFd, &eFd, &handlerdata2, 
											ResponseHelper::GetRemoteHostConnectKO());
										FD_SET(csock, &wFd);
										//goto TERMINATE;
										break;
									}
								}
								FD_SET(csock2, &rFd);
								FD_SET(csock2, &eFd);
								FD_SET(csock2, &wFd);

								if(handlerdata.connectmethod && !php->proxyport){
									handlerdata.KillBuffer();
									nextoperation=PCORE_REMOTE_SSL_CONNECTING;
								}
							}
							else{
								logd<<"ProtocolCore: Upstream handler finished"<<lend;
								FD_SET(csock2, &wFd);
							}

							break;
						case HANDLER_FULL:
							logd<<"ProtocolCore: Upstream  handler full"<<lend;
							FD_CLR(csock, &rFd);/*clearing read*/
							FD_SET(csock2, &wFd);
							break;
						case HANDLER_ERROR:
							logd<<"ProtocolCore: Upstream handler error"<<lend;
							goto TERMINATE;
						default:
							logd<<"ProtocolCore: Upstream handler more"<<lend;
							//read more;
							break;
					}
				}
			}
			//1 Upstream Write - Proxy writing to web server
			if(FD_ISSET(csock2, &wFdx)){
				logd<<"ProtocolCore: Upstream write "<<nextoperation<<lend;

				const char* conokstring = ResponseHelper::GetCONNECTOK();
				int conokstringlength = strlen(conokstring);
				
				if((retv=send(csock2,  buffersend2, bufferbytessend2,0))==-1){
					if(errno==EAGAIN){
						loge<<"ProtocolCore: Upstream write EAGAIN"<<lend;
						//ignore and try again
					}
					else{
						goto TERMINATE;
					}
				}
				else{
					if(retv < bufferbytessend2){
						logd<<"ProtocolCore: Upstream sent  (more) "<<retv<<lend;
						memmove(buffersend2, buffersend2+retv, bufferbytessend2-retv);
						bufferbytessend2 -= retv;
					}
					else{
						logd<<"ProtocolCore: Upstream sent "<<retv<<lend;
						bufferbytessend2=BUFFER_SIZE;
						if(handlerdata.GetHandledData( buffersend2, &bufferbytessend2)==HANDLER_ZERO){
							logd<<"ProtocolCore: Upstream NO MORE Data to send"<<lend;
							FD_CLR(csock2, &wFd);
							FD_SET(csock, &rFd);/*reset read if cleared by FULL*/
						}
						else{
							logd<<"ProtocolCore: Upstream got data "<<bufferbytessend2<<lend;
						}
					}
				}

				switch(nextoperation){
					case PCORE_REMOTE_CONNECTING:
						nextoperation=PCORE_REMOTE_CONNECTED;
						break;
					case PCORE_REMOTE_SSL_CONNECTING:
						logf<<"ProtocolCore: SSL CONNECTED "<<handlerdata.host_address<<":"<<handlerdata.host_port<<lend;
						handlerdata2.KillBuffer();
						handlerdata2.HandleData((char*)conokstring, &conokstringlength);
						FD_CLR(csock2, &wFd);
						FD_SET(csock, &wFd);
						nextoperation=PCORE_REMOTE_CONNECTED;
						break;
					default:
						//PCORE_REMOTE_CONNECTED;
						break;
				}

			}

			//2 Downstream Read - Proxy reading from web server
			if(FD_ISSET(csock2, &rFdx)){
				logd<<"ProtocolCore: Downstream read"<<lend;
				if((bufferbytesrecv2 = recv(csock2, bufferrecv2, BUFFER_SIZE,0))==-1){
					loge<<"ProtocolCore: Donwstream recv error - "<<errno<<lend;
					goto TERMINATE;
				}
				else if(bufferbytesrecv2 == 0 || bufferbytesrecv2 == ECONNRESET){
					logd<<"ProtocolCore: Downstream read close"<<lend;
					if(handlerdata2.CheckHandlerData()==HANDLER_ZERO){
						goto TERMINATE;
					}
					else{
						nextoperation=PCORE_QUIT;
						/*?*/ FD_SET(csock, &wFd);
					}
					FD_CLR(csock2, &rFd);
				}
				else{
					//handlerdata.ResetHeaderCapture();
					logd<<"ProtocolCore: Downstream recv "<<bufferbytesrecv2<<lend;
					switch(handlerdata2.HandleDownstreamData( bufferrecv2, &bufferbytesrecv2)){
						case HANDLER_FINISHED:
							logd<<"ProtocolCore: Downstream handler finished"<<lend;
							handlerdata.ResetHeaderCapture(handlerdata2.GetIsConnect());
							FD_SET(csock, &wFd);
							break;
						case HANDLER_FULL:
							logd<<"ProtocolCore: Downstream handler full"<<lend;
							FD_CLR(csock2, &rFd); /*cleaing read*/
							FD_SET(csock, &wFd);
							break;
						case HANDLER_ERROR:
							logd<<"ProtocolCore: Downstream handler error"<<lend;
							goto TERMINATE;
						default:
							logd<<"ProtocolCore: Downstream handler more"<<lend;
							//read more;
							break;
					}
				}
			}
			//2 Downstream Write - Proxy writing to the client
			if(FD_ISSET(csock, &wFdx)){
				logd<<"ProtocolCore: Downstream write "<<bufferbytessend<<lend;
				if((retv2=send(csock,  buffersend, bufferbytessend,0))==-1){
					if(errno==EAGAIN){
						loge<<"ProtocolCore: Downstream write EAGAIN"<<lend;
						//ignore and try again.
					}
					else
						goto TERMINATE;
				}
				else{
					if(retv2 < bufferbytessend){
						logd<<"ProtocolCore: Donwstream sent (more) "<<retv2<<lend;
						memmove(buffersend, buffersend+retv2, bufferbytessend-retv2);
						bufferbytessend -= retv2;
					}
					else{
						logd<<"ProtocolCore: Downstream sent "<<retv2<<lend;
						bufferbytessend=BUFFER_SIZE;
						if((retv=handlerdata2.GetHandledData( buffersend, &bufferbytessend))==HANDLER_ZERO){
							logd<<"ProtocolCore: Downstream NO MORE Data to send"<<lend;
							FD_CLR(csock, &wFd);
							FD_SET(csock2, &rFd);/*reset read if cleared by FULL*/
							if(nextoperation==PCORE_QUIT){
								goto TERMINATE;
							}
						}
						else{
							//printchararray(buffersend, bufferbytessend, "FUCK");
							logd<<"ProtocolCore: Downstream got data to send "<<bufferbytessend<<lend;
						}
					}
					bytecounter+=retv2;
				}
			}

			if (FD_ISSET(csock2, &eFdx)){
				loge<<"ProtocolCore: Error downstream socket "<<errno<<lend;
				goto TERMINATE;
			}
			if(FD_ISSET(csock, &eFdx)){
				loge<<"ProtocolCore: Error upstream socket "<<errno<<lend;
				goto TERMINATE;
			}
		}
		else{
			if(retvs==0){
				logf<<"Timeout "<< retvs<<" "<< stimeoutx.tv_sec<<" "<< stimeout.tv_sec<<lend;
			}
			else{
				loge<<"ProtocolCore: Socket error select SOCK_ERR ( "<< GetSocketError() <<" )"<<lend;
			}
			goto TERMINATE;
		}
	}


TERMINATE:
	
	logd<<"ProtocolCore: Terminating"<<lend;

	snprintf(tmps128, 128, "{msg=%d}{ip=%s}{up=%d}{down=%d}", FILTER_MSG_BYTES, sp->remoteaddr.ipv6, handlerdata.processedbytes, handlerdata2.processedbytes);
	if(write(sp->commpipe, tmps128, strlen(tmps128))==-1){
		loge<<"ProtocolCore: Pipes write error "<< sp->pid<<lend;
	}
	else{
		//Successful write
	}
	handlerdata.CleanupHandlerData();
	handlerdata2.CleanupHandlerData();
	
	/*?*/close(sp->commpipe);

	if(capi_param)
		free(capi_param);

	CloseSocket(&csock);
	CloseSocket(&csock2);


	return 0;
}


/*************************************************************************
 * Connect to the host using getaddrinfo - original proto
 *************************************************************************/
int ProtocolHTTPHandler::OpenConnectionToHost(int* socket, const char* ip, const int port){
	return OpenConnectionToHost(socket, 0, ip, port); 
}


/*************************************************************************
 * Connect to the host using getaddrinfo -Ex 
 *************************************************************************/
int ProtocolHTTPHandler::OpenConnectionToHost(int* socket, HTTPHandler* hhandler, const char* ip, const int inport){
	int result;
	int retval;
	struct addrinfo hints, *addrresult, *rp;
	struct in_addr inaddr;
	struct in6_addr in6addr;
	int port;
	char* ipaddr;

	if(hhandler){
		ip=hhandler->host_address;
		port=hhandler->host_port;
	}
	else{
		port=inport;
	}

	retval=-1;
	ipaddr=0;
	if(ip!=0 && strlen(ip)!=0){
		if(getaddrinfo(ip, NULL, NULL, &addrresult)==0){

			for (rp = addrresult; rp != NULL; rp = rp->ai_next) {

				ipaddress ipad;
				result=0;	
				switch (rp->ai_family){
					case AF_UNSPEC:
						break;
					case AF_INET:
						//inaddr.s_addr = ((struct sockaddr_in *)(rp->ai_addr))->sin_addr.s_addr;
						if(inet_ntop(AF_INET, &(((struct sockaddr_in *)(rp->ai_addr))->sin_addr), ipad.ipv4, INET_ADDRSTRLEN))
							result=1;
						break;
					case AF_INET6:
						if(inet_ntop(AF_INET6, &(((struct sockaddr_in6 *)(rp->ai_addr))->sin6_addr), ipad.ipv6, INET6_ADDRSTRLEN))
							result=1;
						break;
				}
				if(result){
					ipaddr=(char*)realloc(ipaddr,INET6_ADDRSTRLEN*sizeof(char));
					snprintf(ipaddr,INET6_ADDRSTRLEN, "%s", flattenip(ipad));
					if(hhandler){
						//logd<<hhandler->host_address<<":"<<hhandler->host_port<<lend;
						if(hhandler->Connect(&ipaddr)== HANDLER_REJECT){
							break;
						}
						/*
						((ProtocolHTTPParams*)(hhandler->param1))->capi->ConnectCAP(	hhandler->capi_param1, 
														&(hhandler->host_address),
														&ipaddr,
														&(hhandler->host_port));
						*/
					}

					if((result=ConnectSocket(socket, (char*)flattenip(ipad), port, SOCK_0, 0))!=0 && result!=EALREADY){
						loge<<"ProtocolCore: Failed to connect to "<<ip<<"("<<flattenip(ipad)<<"):"<<port<<" - err "<<result<<lend;
						continue;                                                      
					}
					else{
						logf<<"ProtocolCore: Connected to "<<ip<<"("<<flattenip(ipad)<<"):"<<port<<lend;
						retval=0;
						break;
					}/* Success */
				}
				else{
					loge<<"ProtocolCore: Failed to lookup "<<ip<<lend;
				}
			}
			freeaddrinfo(addrresult); 
		}
		else{
			loge<<"ProtocolCore: getaddrinfo returned 0"<<lend;
		}
	}
	if(ipaddr)
		free(ipaddr);
	return retval;
}



/*************************************************************************
 * Helper function to fillup the handler with pre-configured responses
 *************************************************************************/									
void ProtocolHTTPHandler::ShortCircuit(int* psock1, int* psock2, fd_set* prFd, fd_set* pwFd, fd_set* peFd,
	HTTPHandler* phandlerdata, const char* xstring){
		
	int  xstringlength = strlen(xstring);

	phandlerdata->KillBuffer();	
	phandlerdata->HandleData((char*)xstring, &xstringlength);
	
	/*
	//Remove sock1
	FD_CLR(*psock1, prFd);
	FD_CLR(*psock1, pwFd);
	FD_CLR(*psock1, peFd);
					
	//Add sock2
	FD_SET(*psock2, pwFd);
	*/
}


/*
 * The HTTP Handler Factory
 */

static const char*a[4]= {"GET","PUT", "POST", "CONNECT"};
/*************************************************************************
 * Implementation of th Factory Interface 
 *************************************************************************/
short ProtocolHTTPHandlerFactory::CheckProtocol(const char* buf, unsigned int bufsize){
        char tmp[16];
        strncpy(tmp, buf, 15);
        if(strstr(tmp, "GET"))
                return 1;
        else
                return 0;
}

ProtocolHandlerFunction ProtocolHTTPHandlerFactory::GetFunction(){
        return 0;
}

const char* ProtocolHTTPHandlerFactory::GetProtocolName(){
        return "HTTP";
}

void* ProtocolHTTPHandlerFactory::InitProtocolFactoyHanlder(InitInterface* initp){
	const char* c;

	param1 = (ProtocolHTTPParams*)malloc(sizeof(ProtocolHTTPParams));

	param1->proxyport=atoi((const char*)initp->GetInitParameter("proxy-port"));
	c=initp->GetInitParameter("proxy-ip");
	param1->proxyaddress=(char*)calloc(strlen(c)+1, sizeof(char));
	memcpy(param1->proxyaddress, c, strlen(c));
	param1->inactivetimeout=atoi((const char*)initp->GetInitParameter("inactive-timeout"));
	
	//Set fullforward to zero as this is an HTTP proxy
	param1->fullforward=0;

	param1->capi = new MyCAP();

	param1->capi->InitCAP();

	param1->capi_param1=0;

	return param1;
}

ProtocolHTTPHandlerFactory::ProtocolHTTPHandlerFactory(){
	param1=0;
}


ProtocolHTTPHandlerFactory::~ProtocolHTTPHandlerFactory(){
	if(param1){
		param1->capi->ClearCAP();
		if(param1->proxyaddress)
			free(param1->proxyaddress);
		free(param1);
	}
}


ProtocolHandlerInterface* ProtocolHTTPHandlerFactory::GetHandler(){
	return new ProtocolHTTPHandler();
}

